package com.ideaaedi.springcloud.jd.commonspring.enhancer;

import com.ideaaedi.springcloud.jd.commonds.entity.BaseDTO;
import com.ideaaedi.springcloud.jd.commonds.entity.BasePageDTO;
import com.ideaaedi.springcloud.jd.commonds.entity.ExcelExportSettings;
import com.ideaaedi.springcloud.jd.commonds.entity.PageInfo;
import com.ideaaedi.springcloud.jd.commonds.entity.Result;
import com.ideaaedi.springcloud.jd.commonspring.util.JdContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * excel导出实现
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.1.F
 */
@Slf4j
@RestControllerAdvice
public class ExcelExportSupportAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<Result<PageInfo<?>>> {
    
    private static final String matchPrefix = Result.class.getName() + "<" + PageInfo.class.getName() + "<";
    
    /**
     * Map<controller方法返回值的类型, Pair<当前ResponseBodyAdvice是否支持处理, 对应的具体ExcelExportSupport实现类类型>>
     */
    private static final Map<String, Pair<Boolean, Class<?>>> cache = new ConcurrentHashMap<>(128);
    
    /**
     * 此ThreadLocal用于记录当前请求的excel导出设置
     */
    private static final  ThreadLocal<ExcelExportSettings> excelExportSettingsThreadLocal = new ThreadLocal<>();
    
    @Autowired(required = false)
    private List<ExcelExportExecutor> excelExportExecutorList;
    
    @Override
    public boolean supports(@NotNull MethodParameter methodParameter, @NotNull Type targetType,
                            @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        excelExportSettingsThreadLocal.remove();
        Parameter parameter = methodParameter.getParameter();
        Class<?> type = parameter.getType();
        log.debug("RequestBodyAdviceAdapter supports type -> {}", type == null ? null : type.getName());
        return type != null && BaseDTO.class.isAssignableFrom(type);
    }
    
    @NotNull
    @Override
    public Object afterBodyRead(@NotNull Object body, @NotNull HttpInputMessage inputMessage,
                                @NotNull MethodParameter parameter, @NotNull Type targetType,
                                @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        log.debug("afterBodyRead body -> {}", body);
        if (body instanceof BasePageDTO) {
            ExcelExportSettings excelExportSettings = ((BasePageDTO) body).getExcelExportSettings();
            if (excelExportSettings != null) {
                excelExportSettingsThreadLocal.set(excelExportSettings);
            }
        }
        return body;
    }
    
    @Override
    public boolean supports(MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        Type genericParameterType = returnType.getGenericParameterType();
        // typeName形如： com.ideaaedi.springcloud.jd.commonds.entity.Result<com.ideaaedi.springcloud.jd.commonds
        // .entity.common.PageInfo<com.ideaaedi.springcloud.jd.commonds.entity.user.vo.resp.SysUserListRespVO>>
        String typeName = genericParameterType.getTypeName();
        if (typeName == null) {
            return false;
        }
        Pair<Boolean, Class<?>> pair = cache.get(typeName);
        if (pair != null && pair.getLeft() != null) {
            return pair.getLeft();
        }
        boolean ifSupportExcelExport;
        boolean hitPrefix = typeName.startsWith(matchPrefix);
        Class<?> respClazz = null;
        if (hitPrefix) {
            int fromIndex = matchPrefix.length();
            int gtSymbolIndex = typeName.indexOf(">", fromIndex);
            int ltSymbolIndex = typeName.indexOf("<", fromIndex);
            int toIndex = ltSymbolIndex < 0 ? gtSymbolIndex : Math.min(gtSymbolIndex, ltSymbolIndex);
            String classLongName = typeName.substring(fromIndex, toIndex);
            try {
                respClazz = Class.forName(classLongName);
                ifSupportExcelExport = true;
            } catch (ClassNotFoundException e) {
                log.warn("ClassNotFoundException for classLongName -> {}. curr typeName -> {}. e.getMessage() -> {}", classLongName, typeName, e.getMessage());
                ifSupportExcelExport = false;
            }
        } else {
            ifSupportExcelExport = false;
        }
    
        cache.put(typeName, Pair.of(ifSupportExcelExport, respClazz));
        log.info("put cache typeName -> {}, ifSupportExcelExport -> {}, respClazz -> {}",
                typeName, ifSupportExcelExport, respClazz == null ? null : respClazz.getName());
        return ifSupportExcelExport;
    }
    
    @Override
    @Nullable
    public Result<PageInfo<?>> beforeBodyWrite(@Nullable Result<PageInfo<?>> body, @NotNull MethodParameter returnType,
                                                                          @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                                                          @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
        ExcelExportSettings excelExportSettings = excelExportSettingsThreadLocal.get();
        excelExportSettingsThreadLocal.remove();
        if (excelExportSettings == null || !excelExportSettings.isExcelExportEnable()) {
            return body;
        }
        
        if (body == null) {
            return null;
        }
        Type genericParameterType = returnType.getGenericParameterType();
        String typeName = genericParameterType.getTypeName();
        if (typeName == null || !cache.containsKey(typeName)) {
            // 理论上是不会走到这里的，因为在supports方法已经过滤了
            log.warn("hit 'typeName == null || !cache.containsKey(typeName)'. ignore export excel.");
            return body;
        }
        
        Pair<Boolean, Class<?>> pair = cache.get(typeName);
        Boolean supportExcelExport = pair.getLeft();
        if (!supportExcelExport) {
            // 理论上是不会走到这里的，因为在supports方法已经过滤了
            log.warn("supportExcelExport is false. ignore export excel.");
            return body;
        }
        HttpServletResponse httpServletResponse = JdContextUtil.getHttpServletResponse();
        if (httpServletResponse == null) {
            log.warn("httpServletResponse is null. ignore export excel.");
            return body;
        }
        //noinspection rawtypes
        Class respClazz = pair.getRight();
        PageInfo<?> pageInfo = body.getData();
        List<?> dataList = pageInfo.getDataList();
        if (CollectionUtils.isEmpty(excelExportExecutorList)) {
            return body;
        }
        for (ExcelExportExecutor excelExportExecutor : excelExportExecutorList) {
            //noinspection unchecked
            if (excelExportExecutor.support(respClazz)) {
                //noinspection unchecked
                excelExportExecutor.export(excelExportSettings, respClazz, dataList, httpServletResponse);
                return null;
            }
        }
        return body;
    }
    
}
